<?php

namespace MicroCyanHelper\Plan\HttpCrontab;

use MicroCyanHelper\Core\Kernel\BaseCore;

class App extends BaseCore
{
    /**
     * @var string[] 几种状态
     * push_time - 正在插入数据的时间，不要在过来了
     * members - 需要处理的成员
     * flag_id  - 一般为查询数据操作的标志
     */
    private $flag = [
        'flag_id'=>'flag_id',
        'push_time'=>'push_time',
        'members'=>'members'
    ];

    /**
     * @var float[]|int[]
     * deal_data => 数据处理脚本超时时间，4分钟
     *
     */
    protected $timeout = [
        'exec_max_seconds'=>[10,3600,'建议脚本任务执行时间(exec_max_seconds)不低于10秒，最大不超过3600秒，建议60s'],
        'max_continue_times'=>[1,1000,'单次脚本最大数据处理次数(max_continue_times)不低于1次，最高不超过1000次，建议10'],
    ];
    /**
     * @description 任务脚本开始时间
     * @var int
     */
    protected $start_time = 0;

    public function __construct(array $config=[])
    {
        parent::__construct($config);

        foreach ($this->flag as $flagKey => $flagValue){
            $this->flag[$flagKey] = 'hc:'.$this->namespace.':'.$flagValue;
        }
        foreach ($this->timeout as $key => $timeoutValue){
            if (
                isset($config['timeout'][$key])
                &&is_numeric($config['timeout'][$key])
                &&$config['timeout'][$key]>=$timeoutValue[0]
                &&$config['timeout'][$key]<=$timeoutValue[1]
            ){
                $this->timeout[$key] = $config['timeout'][$key];
                continue;
            }else{
                throw new \Exception($timeoutValue[2]);
            }
        }
    }
    /**
     * @param callable $storeCb
     * @param callable $dealCb
     * @return bool
     * @throws \Exception
     */
    public function start(callable $storeCb,callable $dealCb): bool
    {
        $this->start_time = time();
        /*********************************************
         * 装填数据过程
         *********************************************/
        //判定是否在塞入待处理ID，如果处理时间小于10秒，说明有脚本正在执行push操作，新脚本开始时直接进行阻断操作
        //其实仅判断flag_id>0也是可以进行判断正在插入数据的，但是加上push_time进一步去除同时处理的情况
        $push_time = $this->redis->get($this->flag['push_time']);
        $push_time = is_numeric($push_time)?$push_time:0;
        if (time() - $push_time <= 10) throw new \Exception('正在更新遍历塞入数据ID中，处理数据操作不可进行');
        //到达这里说明既没有在处理数据，也没有在插入数据，那么就要看看上次插入数据时脚本有没有中断，如果有中断，则继续
        while (true){
            $flag_id = $this->redis->get($this->flag['flag_id']);
            $flag_id = is_numeric($flag_id)?$flag_id:0;
            if ($flag_id>0||$this->redis->llen($this->flag['members'])<=0){
                //设置正在插入标志
                $this->redis->set($this->flag['push_time'],time());
                //获取新的标志符号
                $member = $storeCb($flag_id);
                if (isset($member['members'][0])){
                    $this->redis->lpush($this->flag['members'],$member['members']??[]);
                    $this->redis->set($this->flag['flag_id'],$member['flag_id']??0);
                }else{
                    //处理到这里说明该插入的都插入完了，所以可以删除插入的各种标识符
                    $this->redis->set($this->flag['push_time'],0);
                    $this->redis->set($this->flag['flag_id'],0);
                    break;
                }
            }else{
                break;
            }
        }
        //处理到这里说明该插入的都插入完了，所以可以删除插入的各种标识符
        $this->redis->set($this->flag['push_time'],0);
        $this->redis->set($this->flag['flag_id'],0);

        /*********************************************
         * 处理数据过程
         *********************************************/
        while ( true ){
            $this->checkExecMaxSeconds();

            if ($this->timeout['max_continue_times']<=0) throw new \Exception('脚本最大数据处理次数已达到最大');
            $this->timeout['max_continue_times']--;

            $pop_id = $this->redis->rpop($this->flag['members']);
            if (empty($pop_id)) throw new \Exception('没有数据需要处理的，脚本结束了');
            $dealCb($pop_id);
        }
    }
    /**
     * @return bool
     * @throws \Exception
     */
    public function checkExecMaxSeconds(): bool
    {
        if (time() - $this->start_time > $this->timeout['exec_max_seconds']){
            throw new \Exception('本次脚本执行时间已到');
            return false;
        }
        return true;
    }
}